package com.gitee.wsl.android.file

import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.content.ContentResolver
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.os.storage.StorageManager
import android.provider.DocumentsContract
import android.provider.MediaStore
import android.provider.OpenableColumns
import android.provider.Settings
import android.text.TextUtils
import android.util.Size
import androidx.annotation.RawRes
import androidx.annotation.RequiresApi
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import com.gitee.wsl.android.cache.FileCache
import com.gitee.wsl.android.ext.ApplicationInit.application
import com.gitee.wsl.ext.K
import com.gitee.wsl.jvm.ext.base.UTF8
import com.gitee.wsl.ext.base.ifValue
import com.gitee.wsl.ext.string.mapTo
import com.gitee.wsl.service.DownloadService
import com.gitee.wsl.android.ext.store.PathExt
import com.gitee.wsl.android.file.FileManager.IOBase.inputStream2Lines
import com.gitee.wsl.android.file.FileManager.IOBase.readFileContent
import com.gitee.wsl.android.file.FileManager.IOBase.writeFileFromIS
import com.gitee.wsl.ext.any.mapTo
import com.gitee.wsl.jvm.io.file.ZipFileManager
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.suspendCancellableCoroutine
import okio.*
import okio.Path.Companion.toPath
import timber.log.Timber
import java.io.*
import java.io.FileNotFoundException
import java.io.IOException
import java.nio.charset.Charset
import java.util.*
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.io.use


object FileManager {

    fun createCacheFile(fileName: String?,createdCallback:(File)->Unit):Uri{
        val file =(fileName!=null).ifValue( PathExt.cacheFilePath(fileName!!), PathExt.randomFilePath())
        createdCallback(file)
        return file.toUri()
    }

    /**
     * Return the file by path.
     *
     * @param filePath The path of file.
     * @return the file
     */
    fun getFileByPath(filePath: String): File {
        return File(filePath)
    }

    /**
     * Rename the file.
     *
     * @param filePath The path of file.
     * @param newName  The new name of file.
     * @return `true`: success<br></br>`false`: fail
     */
    fun rename(filePath: String, newName: String): Boolean {
        return rename(getFileByPath(filePath), newName)
    }

    /**
     * Rename the file.
     *
     * @param file    The file.
     * @param newName The new name of file.
     * @return `true`: success<br></br>`false`: fail
     */
    fun rename(file: File, newName: String): Boolean {
        // file doesn't exist then return false
        if (!file.exists()) return false
        // the new name is space then return false
        if (newName.isEmpty()) return false
        // the new name equals old name then return true
        if (newName == file.name) return true
        file.parent?:return false
        val newFile = File(file.parent!! + File.separator.toString() + newName)
        // the new name of file exists then return false
        return (!newFile.exists()
                && file.renameTo(newFile))
    }

    /**
     * Return whether it is a directory.
     *
     * @param dirPath The path of directory.
     * @return `true`: yes<br></br>`false`: no
     */
    fun isDir(dirPath: String): Boolean {
        return isDir(getFileByPath(dirPath))
    }

    /**
     * Return whether it is a directory.
     *
     * @param file The file.
     * @return `true`: yes<br></br>`false`: no
     */
    fun isDir(file: File): Boolean {
        return file.exists() && file.isDirectory
    }

    /**
     * Return whether it is a file.
     *
     * @param filePath The path of file.
     * @return `true`: yes<br></br>`false`: no
     */
    fun isFile(filePath: String): Boolean {
        return isFile(getFileByPath(filePath))
    }

    /**
     * Return whether it is a file.
     *
     * @param file The file.
     * @return `true`: yes<br></br>`false`: no
     */
    fun isFile(file: File): Boolean {
        return  file.exists() && file.isFile
    }

    /**
     * Create a directory if it doesn't exist, otherwise do nothing.
     *
     * @param dirPath The path of directory.
     * @return `true`: exists or creates successfully<br></br>`false`: otherwise
     */
    fun createOrExistsDir(dirPath: String): Boolean {
        return createOrExistsDir(getFileByPath(dirPath))
    }

    /**
     * Create a directory if it doesn't exist, otherwise do nothing.
     *
     * @param file The file.
     * @return `true`: exists or creates successfully<br></br>`false`: otherwise
     */
    fun createOrExistsDir(file: File): Boolean {
        return if (file.exists()) file.isDirectory else file.mkdirs()
    }

    /**
     * Create a file if it doesn't exist, otherwise do nothing.
     *
     * @param filePath The path of file.
     * @return `true`: exists or creates successfully<br></br>`false`: otherwise
     */
    fun createOrExistsFile(filePath: String): Boolean {
        return createOrExistsFile(getFileByPath(filePath))
    }

    /**
     * Create a file if it doesn't exist, otherwise do nothing.
     *
     * @param file The file.
     * @return `true`: exists or creates successfully<br></br>`false`: otherwise
     */
    fun createOrExistsFile(file: File): Boolean {
        if (file.exists()) return file.isFile
        file.parentFile?:return false
        if(!createOrExistsDir(file.parentFile!!)) return false
        return  try {
            file.createNewFile()
        } catch (e: IOException) {
            e.printStackTrace()
            false
        }
    }

    /**
     * Create a file if it doesn't exist, otherwise delete old file before creating.
     *
     * @param file The file.
     * @return `true`: success<br></br>`false`: fail
     */
    fun createFileByDeleteOldFile(file: File): Boolean {
        // file exists and unsuccessfully delete then return false
        if (file.exists() && !file.delete()) return false
        file.parentFile?:return false
        if(!createOrExistsDir(file.parentFile!!)) return false

        return  try {
            file.createNewFile()
        } catch (e: IOException) {
            e.printStackTrace()
            false
        }
    }

    /**
     * 复制单个文件
     * @param inputStream  文件流 ( 被复制 )
     * @param destFilePath 目标文件地址
     * @param overlay      如果目标文件存在, 是否覆盖
     * @return `true` success, `false` fail
     */
    fun copyFile(
        inputStream: InputStream,
        destFilePath: String,
        overlay: Boolean = false
    ): Boolean {
        val destFile = File(destFilePath)
        // 如果属于文件夹则跳过
        if (destFile.isDirectory) {
            return false
        }
        if (destFile.exists()) {
            // 如果目标文件存在并允许覆盖
            if (overlay) {
                // 删除已经存在的目标文件, 无论目标文件是目录还是单个文件
                destFile.delete()
            } else { // 如果文件存在, 但是不覆盖, 则返回 false 表示失败
                return false
            }
        } else {
            // 如果目标文件所在目录不存在, 则创建目录
            if (destFile.parentFile?.exists()==false) {
                // 目标文件所在目录不存在
                if (!destFile.parentFile!!.mkdirs()) {
                    // 复制文件失败: 创建目标文件所在目录失败
                    return false
                }
            }
        }
        // 复制文件
        var len: Int // 读取的字节数
        val `is`: InputStream = inputStream
        var os: OutputStream? = null
        return try {
            os = FileOutputStream(destFile)
            val buffer = ByteArray(1024)
            while (`is`.read(buffer).also { len = it } != -1) {
                os.write(buffer, 0, len)
            }
            true
        } catch (e: java.lang.Exception) {
            Timber.d( e, "copyFile")
            false
        } finally {
            os?.close()
            `is`.close()
        }
    }

    /**
     * 复制单个文件
     * @param srcFilePath  待复制的文件地址
     * @param destFilePath 目标文件地址
     * @param overlay      如果目标文件存在, 是否覆盖
     * @return `true` success, `false` fail
     */
    fun copyFile(
        srcFilePath: String,
        destFilePath: String,
        overlay: Boolean = false
    ): Boolean {
        val file = File(srcFilePath)
        return if (!file.isFile)
                  false
               else
                  copyFile(srcFilePath.byteInputStream(), destFilePath, overlay)
    }

    /**
     * 复制文件夹
     * @param srcFolderPath  待复制的文件夹地址
     * @param destFolderPath 存储目标文件夹地址
     * @param overlay        如果目标文件存在, 是否覆盖
     * @return `true` success, `false` fail
     */
    fun copyFolder(
        srcFolderPath: String,
        destFolderPath: String,
        overlay: Boolean
    ): Boolean {
        return copyFolder(srcFolderPath, destFolderPath, srcFolderPath, overlay)
    }

    /**
     * 复制文件夹
     * @param srcFolderPath  待复制的文件夹地址
     * @param destFolderPath 存储目标文件夹地址
     * @param sourcePath     源文件地址 ( 用于保递归留记录 )
     * @param overlay        如果目标文件存在, 是否覆盖
     * @return `true` success, `false` fail
     */
    private fun copyFolder(
        srcFolderPath: String,
        destFolderPath: String,
        sourcePath: String,
        overlay: Boolean
    ): Boolean {
        val srcFile = File(srcFolderPath)
        // 判断源文件是否存在
        if (!srcFile.exists()) {
            return false
        } else if (!srcFile.isDirectory) { // 不属于文件夹则跳过
            return false
        }
        // 判断目标文件是否存在
        val destFile = File(destFolderPath)
        // 如果文件夹没创建, 则创建
        if (!destFile.exists()) {
            // 允许创建多级目录
            destFile.mkdirs()
        }
        // 判断是否属于文件夹 ( 不属于文件夹则跳过 )
        if (!destFile.isDirectory) {
            return false
        }
        // 判断是否存在
        if (srcFile.exists()) {
            // 获取文件路径
            val files = srcFile.listFiles()
            // 防止不存在文件
            if (files != null && files.isNotEmpty()) {
                // 进行遍历
                for (file in files) {
                    // 文件存在才进行处理
                    if (file.exists()) {
                        // 属于文件夹
                        if (file.isDirectory) {
                            copyFolder(file.absolutePath, destFolderPath, sourcePath, overlay)
                        } else { // 属于文件
                            // 复制的文件地址
                            val filePath = file.absolutePath
                            // 获取源文件地址并且进行判断
                            var dealSource = File(sourcePath).absolutePath
                            // 属于最前才进行处理
                            if (filePath.indexOf(dealSource) == 0) {
                                // 获取处理后的地址
                                dealSource = filePath.substring(dealSource.length)
                                // 获取需要复制保存的地址
                                val savePath = File(destFolderPath, dealSource).absolutePath
                                // 进行复制文件
                                val result = copyFile(filePath, savePath, overlay)
                            }
                        }
                    }
                }
            }
        }
        return true
    }

    /**
     * 删除文件
     * @param filePath 文件路径
     * @return `true` success, `false` fail
     */
    fun deleteFile(filePath: String): Boolean {
        return deleteFile(getFileByPath(filePath))
    }

    /**
     * 删除文件
     * @param file 文件
     * @return `true` success, `false` fail
     */
    fun deleteFile(file: File): Boolean {
        // 文件存在, 并且不是目录文件, 则直接删除
        return if (file.exists() && !file.isDirectory) {
            file.delete()
        } else false
    }

    /**
     * 删除文件夹
     * @param filePath 文件路径
     * @return `true` success, `false` fail
     */
    fun deleteFolder(filePath: String): Boolean {
        return deleteFolder(getFileByPath(filePath))
    }

    /**
     * 删除文件夹
     * @param file 文件
     * @return `true` success, `false` fail
     */
    fun deleteFolder(file: File): Boolean {
            try {
                // 文件存在, 并且不是目录文件, 则直接删除
                if (file.exists()) {
                    return if (file.isDirectory) { // 属于文件目录
                        val files = file.listFiles()
                        if (files != null) {
                            for (deleteFile in files) {
                                deleteFolder(deleteFile.path)
                            }
                        }
                        file.delete()
                    } else { // 属于文件
                        deleteFile(file)
                    }
                }
            } catch (e: java.lang.Exception) {
                Timber.d( e, "deleteFolder")
            }
        return false
    }

    /**
     * 移动 ( 剪切 ) 文件
     * @param srcFilePath  待移动的文件地址
     * @param destFilePath 目标文件地址
     * @param overlay      如果目标文件存在, 是否覆盖
     * @return `true` success, `false` fail
     */
    fun moveFile(
        srcFilePath: String,
        destFilePath: String,
        overlay: Boolean
    ): Boolean {
        // 复制文件
        return if (copyFile(srcFilePath, destFilePath, overlay)) {
            // 删除文件
            deleteFile(srcFilePath)
        } else false
    }

    /**
     * 移动 ( 剪切 ) 文件夹
     * @param srcFilePath  待移动的文件夹地址
     * @param destFilePath 存储目标文件夹地址
     * @param overlay      如果目标文件存在, 是否覆盖
     * @return `true` success, `false` fail
     */
    fun moveFolder(
        srcFilePath: String,
        destFilePath: String,
        overlay: Boolean
    ): Boolean {
        // 复制文件夹
        return if (copyFolder(srcFilePath, destFilePath, overlay)) {
            // 删除文件夹
            deleteFolder(srcFilePath)
        } else false
    }

    //统一处理权限
    fun isStoragePermissionGranted(activity: Activity): Boolean {
        val context = activity.applicationContext
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            if (!Environment.isExternalStorageManager()) {
                val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
                val uri = Uri.fromParts("package", activity.packageName, null)
                intent.data = uri
                activity.startActivityForResult(intent, 1)
                false
            } else {
                // 有外部存储的权限
                true
            }
        } else {
            val readPermissionCheck = ContextCompat.checkSelfPermission(
                context,
                Manifest.permission.READ_EXTERNAL_STORAGE
            )
            val writePermissionCheck = ContextCompat.checkSelfPermission(
                context,
                Manifest.permission.WRITE_EXTERNAL_STORAGE
            )
            if (readPermissionCheck == PackageManager.PERMISSION_GRANTED
                && writePermissionCheck == PackageManager.PERMISSION_GRANTED
            ) {
                Timber.d("Permission is granted")
                true
            } else {
                Timber.d( "Permission is revoked")
                ActivityCompat.requestPermissions(
                    activity, arrayOf(
                        Manifest.permission.READ_EXTERNAL_STORAGE,
                        Manifest.permission.WRITE_EXTERNAL_STORAGE
                    ), 1
                )
                false
            }
        }
    }

    object Assets{
        /**
         * Copy the file from assets.
         *
         * @param assetsFilePath The path of file in assets.
         * @param destFilePath   The path of destination file.
         * @return `true`: success<br></br>`false`: fail
         */
        fun copyFileFromAssets(assetsFilePath: String, destFilePath: String): Boolean {
            var res = true
            try {
                val assets: Array<String>? = application.resources.assets.list(assetsFilePath)
                if (assets != null && assets.isNotEmpty()) {
                    for (asset in assets) {
                        res = res and copyFileFromAssets(
                            "$assetsFilePath/$asset",
                            "$destFilePath/$asset"
                        )
                    }
                } else {
                    res = writeFileFromIS(destFilePath,application.resources.assets.open(assetsFilePath))
                }
            } catch (e: IOException) {
                e.printStackTrace()
                res = false
            }
            return res
        }

        /**
         * Return the content of assets.
         *
         * @param assetsFilePath The path of file in assets.
         * @return the content of assets
         */
        fun readAssets2String(assetsFilePath: String): String? {
            return readAssets2String(assetsFilePath, null)
        }

        /**
         * Return the content of assets.
         *
         * @param assetsFilePath The path of file in assets.
         * @param charsetName    The name of charset.
         * @return the content of assets
         */
        fun readAssets2String(assetsFilePath: String, charsetName: String?): String? {
            return try {
                val inputStream: InputStream = application.resources.assets.open(assetsFilePath)
                val charset = mapTo { Charset.forName(charsetName) } ?:Charset.defaultCharset()
                //val charset = if (charsetName.isNullOrEmpty()) Charset.defaultCharset() else Charset.forName(charsetName)
                return readFileContent(inputStream,charset)
            } catch (e: IOException) {
                e.printStackTrace()
                null
            }
        }

        /**
         * Return the content of file in assets.
         *
         * @param assetsPath The path of file in assets.
         * @return the content of file in assets
         */
        fun readAssets2List(assetsPath: String): List<String> {
            return readAssets2List(assetsPath, null)
        }

        /**
         * Return the content of file in assets.
         *
         * @param assetsPath  The path of file in assets.
         * @param charsetName The name of charset.
         * @return the content of file in assets
         */
        fun readAssets2List(
            assetsPath: String,
            charsetName: String?
        ): List<String> {
            return try {
                inputStream2Lines(
                    application.resources.assets.open(assetsPath), charsetName
                )
            } catch (e: IOException) {
                e.printStackTrace()
                Collections.emptyList()
            }
        }


        /**
         * Copy the file from raw.
         *
         * @param resId        The resource id.
         * @param destFilePath The path of destination file.
         * @return `true`: success<br></br>`false`: fail
         */
        fun copyFileFromRaw(@RawRes resId: Int, destFilePath: String): Boolean {
            return writeFileFromIS(
                destFilePath,
                application.resources.openRawResource(resId)
            )
        }

        /**
         * Return the content of resource in raw.
         *
         * @param resId The resource id.
         * @return the content of resource in raw
         */
        fun readRaw2String(@RawRes resId: Int): String? {
            return readRaw2String(resId, null)
        }

        /**
         * Return the content of resource in raw.
         *
         * @param resId       The resource id.
         * @param charsetName The name of charset.
         * @return the content of resource in raw
         */
        fun readRaw2String(@RawRes resId: Int, charsetName: String?): String {
            val inputStream: InputStream = application.resources.openRawResource(resId)
            val charset= if (charsetName.isNullOrEmpty()) Charset.defaultCharset()
                   else Charset.forName(charsetName)
            return readFileContent(inputStream,charset)
            /*val bytes: ByteArray = UtilsBridge.inputStream2Bytes(`is`) ?: return null
            return if (charsetName.isNullOrEmpty()) {
                String(bytes)
            } else {
                try {
                    String(bytes, Charset.forName(charsetName))
                } catch (e: UnsupportedEncodingException) {
                    e.printStackTrace()
                    null
                }
            }*/
        }

        /**
         * Return the content of resource in raw.
         *
         * @param resId The resource id.
         * @return the content of file in assets
         */
        fun readRaw2List(@RawRes resId: Int): List<String> {
            return readRaw2List(resId, "")
        }

        /**
         * Return the content of resource in raw.
         *
         * @param resId       The resource id.
         * @param charsetName The name of charset.
         * @return the content of file in assets
         */
        fun readRaw2List(
            @RawRes resId: Int,
            charsetName: String?
        ): List<String> {
            return inputStream2Lines(
                application.resources.openRawResource(resId), charsetName
            )
        }
    }



    object IOBase{

        fun inputStream2Lines(
            inputStream: InputStream, charsetName:String?
        ): List<String>{
            val bufferedSource = inputStream.source().buffer()
            val retList = mutableListOf<String>()
            while (true) {
                bufferedSource.readUtf8Line()?.apply { retList.add(this) } ?:break
                //com.gitee.wsl.ext.string.mapTo { retList.add(it) } ?:break
            }
            return retList
        }

       fun writeFileFromIS( destFilePath:String,inputStream: InputStream):Boolean{
           try {
               val file = destFilePath.toPath().toFile()
               val bufferedSource = inputStream.source().buffer()
               val bufferedSink: BufferedSink = file.sink().buffer()
               bufferedSink.writeAll(bufferedSource)
               bufferedSink.close()
           }catch (e:IOException){
             e.printStackTrace()
             return false
           }
           return true
       }

        @Throws(IOException::class)
        fun readFileContent(
            fileInputStream: InputStream,
            charset: Charset
        ): String {
            BufferedReader(InputStreamReader(fileInputStream, charset)).use { br ->
                val sb = StringBuilder()
                var line: String = ""
                while (br.readLine().also { line = it } != null) {
                    sb.append(line)
                    sb.append('\n')
                }
                return sb.toString()
            }
        }

    }


    object StringData {

        fun writeToFile(context: Context, uri: Uri, content: String,charset: Charset = UTF8):Boolean {
            try {
                val contentResolver = context.contentResolver
                contentResolver.openFileDescriptor(uri, "w")?.use {
                    FileOutputStream(it.fileDescriptor).use { fOut ->
                        val writer = fOut.writer(charset = UTF8)
                        writer.write(content)
                        writer.close()
                    }
                    return true
                }
            } catch (e: FileNotFoundException) {
                e.printStackTrace()
            } catch (e: IOException) {
                e.printStackTrace()
            }
            return false
        }

        fun readFile(
            context: Context,
            uri: Uri,
            charset: Charset = UTF8
        ): String? {
            return try {
                val contentResolver = context.contentResolver

                var fileContent: String? = null

                contentResolver.openFileDescriptor(uri, "r")?.use {
                    FileInputStream(it.fileDescriptor).use { fileInputStream ->
                        fileContent = readFileContent(
                            fileInputStream = fileInputStream,
                            charset = charset
                        )
                    }
                }

                fileContent
            } catch (e: FileNotFoundException) {
                e.printStackTrace()
                null
            } catch (e: IOException) {
                e.printStackTrace()
                null
            }
        }

    }

    object JsonData {
            fun createJsonDataFile(
                context: Context,
                jsonString: String,
                fileNamePrefix: String = "backup_data",
                fileNameSuffix: String = ".json",
                outputDir: File = context.cacheDir
            ): File {
                val file = File.createTempFile(fileNamePrefix, fileNameSuffix, outputDir)
                file.writeText(jsonString, Charsets.UTF_16)
                return file
            }
    }
}

inline fun Uri.readContent(charset: Charset = UTF8):String?=
    FileManager.StringData.readFile(application, this, charset)

fun Context.getFileName(uri: Uri): String? = when (uri.scheme) {
    ContentResolver.SCHEME_CONTENT -> getContentFileName(uri)
    else -> uri.path?.let(::File)?.name
}

private fun Context.getContentFileName(uri: Uri): String? = runCatching {
    contentResolver.query(uri, null, null, null, null)?.use { cursor ->
        cursor.moveToFirst()
        return@use cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)
            .let(cursor::getString)
    }
}.getOrNull()


 //* 媒体类型	    Uri 路径	MediaStore                      内部类常量	                                    文件类型 (MimeType)	    默认存储目录	允许存储目录
 //* Image(图片)	'content://media/external/images/media'	MediaStore.Images.Media.EXTERNAL_CONTENT_URI	    'image/*'	        Pictures	DCIM、Pictures
 //* Audio(音频)	'content://media/external/audio/media'	MediaStore.Audio.Media.EXTERNAL_CONTENT_URI	        'audio/*'	        Music	    Alarms、Music、Notifications、Podcasts、Ringtones
 //* Video(视频)	'content://media/external/video/media'	MediaStore.Video.Media.EXTERNAL_CONTENT_URI	        'video/*'	        Movies	    DCIM 、Movies
 //* Files(下载)	'content://media/external/downloads'    MediaStore.Downloads.EXTERNAL_CONTENT_URI	        'file/*'	        Download	Download
 object MediaFileManager{

    /**
     * Every added file should be scanned to be seen immediately in MediaStore or the system file
     * navigator used by intents like ACTION_OPEN_DOCUMENT
     */
    suspend fun scanFilePath(context: Context, path: String, mimeType: String): Uri {
        return suspendCancellableCoroutine { continuation ->
            MediaScannerConnection.scanFile(
                context,
                arrayOf(path),
                arrayOf(mimeType)
            ) { _, scannedUri ->
                if (scannedUri != null) {
                    continuation.resume(scannedUri)
                } else {
                    continuation.resumeWithException(Exception("Scanning isn't supposed to fail"))
                }
            }
        }
    }

     /**
      * 只能获取到文件名称
      */
     fun queryFileName4Uri(context: Context, uri: Uri, nameKey: String): String? {
         if (DocumentsContract.isTreeUri(uri)) {
             val path = uri.path ?: return null
             return path.substring(path.lastIndexOf(":") + 1)
         }
         context.contentResolver?.query(uri, null, null, null, null)?.use {
             val isHas = it.moveToFirst()
             if (isHas) {
                 val nameIndex = it.getColumnIndex(nameKey)
                 if (nameIndex != -1) {
                     return it.getString(nameIndex)
                 }
             }
         }
         return null
     }

     fun queryFilePath4Uri(context: Context, uri: Uri, pathKey: String): String? {
         context.contentResolver?.query(uri, null, null, null, null)?.use {
             val isHas = it.moveToFirst()
             if (isHas) {
                 val dataIndex = it.getColumnIndex(pathKey)
                 if (dataIndex != -1) {
                     val data = it.getString(dataIndex)
                     if (!TextUtils.isEmpty(data)) {
                         return data
                     }
                 }
             }
         }
         return uri.path
     }

     /**
      * 加载出来的是源文件
      * 如果是图片，转换成bitmap，是原图尺寸
      */
     fun queryFile4Uri(context: Context, uri: Uri): FileDescriptor? {
         return try {
             context.contentResolver?.openFileDescriptor(
                 uri,
                 "r"
             )?.fileDescriptor
         } catch (e: FileNotFoundException) {
             null
         }
     }


    //查询媒体库所有图片和视频
    //图片uri
    @SuppressLint("Range")
    fun getAllPhotoAndVideo(context: Context): List<PhotoEntity> {
        val result: MutableList<PhotoEntity> = ArrayList<PhotoEntity>()
        val uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
        val cursor = context.contentResolver.query(uri, null, null, null, null)
        if (cursor != null) {
            while (cursor.moveToNext()) {

                val id = cursor.getInt(cursor.getColumnIndex(MediaStore.Images.Media._ID))
                val path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA))
                val duration = cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media.DURATION))
                result.add(PhotoEntity(id, path, isFavorite = false, false, 0))
            }
            cursor.close()
        }
        val videoUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
        val videoCursor = context.contentResolver.query(videoUri, null, null, null, null)
        if (videoCursor != null) {
            while (videoCursor.moveToNext()) {
                val id = videoCursor.getInt(videoCursor.getColumnIndex(MediaStore.Images.Media._ID))
                val path = videoCursor.getString(videoCursor.getColumnIndex(MediaStore.Images.Media.DATA))
                val duration = videoCursor.getLong(videoCursor.getColumnIndex(MediaStore.Images.Media.DURATION))
                result.add(PhotoEntity(id, path, isFavorite = false, true, duration))
            }
            videoCursor.close()
        }
        return result
    }

   /**
  * MediaStore.Images.Media._ID：磁盘上文件的路径
  * MediaStore.Images.Media.DATA：磁盘上文件的路径
  * MediaStore.Images.Media.DATE_ADDED：文件添加到 media provider 的时间（单位秒）
  * MediaStore.Images.Media.DATE_MODIFIED：文件最后一次修改单元的时间
  * MediaStore.Images.Media.DISPLAY_NAME：文件的显示名称
  * MediaStore.Images.Media.HEIGHT：图像 / 视频的高度，以像素为单位
  * MediaStore.Images.Media.MIME_TYPE：文件的 MIME 类型
  * MediaStore.Images.Media.SIZE：文件的字节大小
  * MediaStore.Images.Media.TITLE：标题
  * MediaStore.Images.Media.WIDTH：图像 / 视频的宽度，以像素为单位。
    *
  * MediaStore.Video.Media.TITLE 名称
  * MediaStore.Video.Media.DURATION 总时长
  * MediaStore.Video.Media.DATA 地址
  * MediaStore.Video.Media.SIZE 大小
  * MediaStore.Video.Media.WIDTH：视频的宽度，以像素为单位。
  * MediaStore.Video.Media.HEIGHT：视频的高度，以像素为单位
    *
  * MediaStore.Audio.Media.TITLE：歌名
  * MediaStore.Audio.Media.ARTIST：歌手
  * MediaStore.Audio.Media.DURATION：总时长
  * MediaStore.Audio.Media.DATA：地址
  * MediaStore.Audio.Media.SIZE：大小
  **/
   data class PhotoEntity(val id: Int, val path: String, val isFavorite: Boolean = false, val isVideo: Boolean = false, val duration: Long)

     /**
      * mediaStore接口保存
      */
     fun saveBitmap2Public(context: Context, bitmap: Bitmap, dirName: String): Uri? {
         //TODO 存在插入失败的情况，UNIQUE constraint failed: files._data。
         // 由此可见，这些属性不能重复。特别是 DISPLAY_NAME
         val values = ContentValues().apply {
             put(MediaStore.Images.Media.TITLE, "title_1")
             put(MediaStore.Images.Media.DISPLAY_NAME, "sample_${System.currentTimeMillis()}.png")
             put(MediaStore.Images.Media.MIME_TYPE, "image/png")
             put(MediaStore.Images.Media.RELATIVE_PATH, "$dirName/sample")
         }
         val contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
         var result: Uri? = null
         context.contentResolver?.let { resolver ->
             resolver.insert(
                 contentUri,
                 values
             )?.let { insertUri ->
                 result = insertUri
                 resolver.openOutputStream(insertUri)?.use {
                     bitmap.compress(Bitmap.CompressFormat.PNG, 100, it)
                 }
             }
         }
         return result
     }

     fun saveBitmap2Uir(context: Context, saveUri: Uri, bitmap: Bitmap) {
         context.contentResolver?.let { resolver ->
             resolver.openOutputStream(saveUri)?.use {
                 bitmap.compress(Bitmap.CompressFormat.PNG, 100, it)
             }
         }
     }

     @RequiresApi(Build.VERSION_CODES.Q)
     fun saveFile2Public(
         context: Context,
         source: InputStream,
         dirName: String,
         suffix: String
     ): Uri? {
         val values = if (TextUtils.equals(dirName, Environment.DIRECTORY_DOWNLOADS)) {
             buildDownloadContentValues(dirName, suffix)
         } else {
             buildDocumentsContentValues(dirName, suffix)
         }

         val contentUri = if (TextUtils.equals(dirName, Environment.DIRECTORY_DOWNLOADS)) {
             MediaStore.Downloads.EXTERNAL_CONTENT_URI
         } else {
             MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL)
         }

         var result: Uri? = null
         context.contentResolver?.let { resolver ->
             resolver.insert(
                 contentUri,
                 values
             )?.let { insertUri ->
                 result = insertUri
                 resolver.openOutputStream(insertUri)?.use { outPut ->
                     var read: Int = -1
                     val buffer = ByteArray(2048)
                     while (source.read(buffer).also { read = it } != -1) {
                         outPut.write(buffer, 0, read)
                     }
                 }
             }
         }
         return result
     }

     fun saveFile2Uri(
         context: Context,
         saveUri: Uri,
         source: InputStream
     ) {
         context.contentResolver?.let { resolver ->
             resolver.openOutputStream(saveUri)?.use { outPut ->
                 var read: Int = -1
                 val buffer = ByteArray(2048)
                 while (source.read(buffer).also { read = it } != -1) {
                     outPut.write(buffer, 0, read)
                 }
             }
         }
     }

     fun saveVideo2Public(
         context: Context,
         source: InputStream,
         dirName: String,
         suffix: String
     ): Uri? {
         val values = ContentValues().apply {
             put(MediaStore.Video.Media.TITLE, "title_1")
             put(
                 MediaStore.Video.Media.DISPLAY_NAME,
                 "sample_${System.currentTimeMillis()}.${suffix}"
             )
             put(MediaStore.Video.Media.MIME_TYPE, "video/$suffix")
             put(MediaStore.Video.Media.RELATIVE_PATH, "$dirName/sample")
         }
         val contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
         var result: Uri? = null
         context.contentResolver?.let { resolver ->
             resolver.insert(
                 contentUri,
                 values
             )?.let { insertUri ->
                 result = insertUri
                 resolver.openOutputStream(insertUri)?.use { outPut ->
                     var read: Int = -1
                     val buffer = ByteArray(2014)
                     while (source.read(buffer).also { read = it } != -1) {
                         outPut.write(buffer, 0, read)
                     }
                 }
             }
         }
         return result
     }

     fun saveVideo2Uri(
         context: Context,
         saveUri: Uri,
         source: InputStream
     ) {
         context.contentResolver?.let { resolver ->
             resolver.openOutputStream(saveUri)?.use { outPut ->
                 var read: Int = -1
                 val buffer = ByteArray(2014)
                 while (source.read(buffer).also { read = it } != -1) {
                     outPut.write(buffer, 0, read)
                 }
             }
         }
     }

     fun saveAudio2Public(
         context: Context,
         source: InputStream,
         dirName: String,
         suffix: String
     ): Uri? {
         val values = ContentValues().apply {
             put(MediaStore.Audio.Media.TITLE, "title_1")
             put(
                 MediaStore.Audio.Media.DISPLAY_NAME,
                 "sample_${System.currentTimeMillis()}.${suffix}"
             )
             put(MediaStore.Audio.Media.MIME_TYPE, "audio/$suffix")
             put(MediaStore.Audio.Media.RELATIVE_PATH, "$dirName/sample")
         }
         val contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
         var result: Uri? = null
         context.contentResolver?.let { resolver ->
             resolver.insert(
                 contentUri,
                 values
             )?.let { insertUri ->
                 result = insertUri
                 resolver.openOutputStream(insertUri)?.use { outPut ->
                     var read: Int = -1
                     val buffer = ByteArray(2048)
                     while (source.read(buffer).also { read = it } != -1) {
                         outPut.write(buffer, 0, read)
                     }
                 }
             }
         }
         return result
     }

     fun saveAudio2Uri(
         context: Context,
         saveUri: Uri,
         source: InputStream
     ) {
         context.contentResolver?.let { resolver ->
             resolver.openOutputStream(saveUri)?.use { outPut ->
                 var read: Int = -1
                 val buffer = ByteArray(2048)
                 while (source.read(buffer).also { read = it } != -1) {
                     outPut.write(buffer, 0, read)
                 }
             }
         }
     }

     private fun buildDownloadContentValues(dirName: String, suffix: String): ContentValues {
         val values = ContentValues()
         values.put(MediaStore.Images.Media.TITLE, "title_1")
         values.put(
             MediaStore.Downloads.DISPLAY_NAME,
             "sample_${System.currentTimeMillis()}.$suffix"
         )
         values.put(
             MediaStore.Downloads.RELATIVE_PATH,
             "$dirName/sample"
         )
         return values
     }

     private fun buildDocumentsContentValues(dirName: String, suffix: String): ContentValues {
         val values = ContentValues()
         values.put(MediaStore.Images.Media.TITLE, "title_1")
         values.put(
             MediaStore.Downloads.DISPLAY_NAME,
             "sample_${System.currentTimeMillis()}.$suffix"
         )
         values.put(
             MediaStore.Downloads.RELATIVE_PATH,
             "$dirName/sample"
         )
         return values
     }

     /**
      *  透明背景的图片加载出来是黑色的。
      */
     @RequiresApi(Build.VERSION_CODES.Q)
     fun loadThumbnail4Uri(context: Context, uri: Uri): Bitmap? {
         return try {
             context.contentResolver.loadThumbnail(
                 uri,
                 Size(100, 100),
                 null
             )
         } catch (e: FileNotFoundException) {
             null
         }
     }

}



fun ZipFileManager.zip(context: Context, zipFile: Uri, files: List<File>) {
    context.contentResolver.openFileDescriptor(zipFile, ZipFileManager.MODE_WRITE).use { descriptor ->
        descriptor?.fileDescriptor?.let {
            ZipOutputStream(BufferedOutputStream(FileOutputStream(it))).use { outStream ->
                ZipFileManager.zip(outStream, files)
            }
        }
    }
}


fun ZipFileManager.unzip(context: Context, zipFilePath: Uri, unzipLocation: File) {
    context.contentResolver.openFileDescriptor(zipFilePath,
        ZipFileManager.MODE_READ
    ).use { descriptor ->
        descriptor?.fileDescriptor?.let {
            ZipInputStream(BufferedInputStream(FileInputStream(it))).use { inStream ->
                ZipFileManager.unzip(inStream, unzipLocation)
            }
        }
    }
}



val storageManager: StorageManager get()=  application.getSystemService(Context.STORAGE_SERVICE) as StorageManager

fun netCacheFile(fileUri:String, defaultFile:File?, cacheFileName:String, downloadService: DownloadService, cache: FileCache): Flow<File> = flow{
     if(cache.isFileCache(cacheFileName,cacheFileName)) {
            emit(cache.getFile(cacheFileName, cacheFileName))
     } else {
            val cacheFile = cache.getFile(cacheFileName, cacheFileName)
            downloadService.startDownLoad(fileUri).onCompleted {
                FileManager.copyFile(this.downloadUrl, cacheFile.absolutePath)
                runBlocking(K.Default){
                    emit(cacheFile)
                }
            }.onError {
                cacheFile.delete()
                defaultFile?.let {
                    runBlocking(K.Default) {
                        emit(it)
                    }
                }
            }
     }
}